package com.cskaoyan.mall.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.cskaoyan.mall.common.constant.ResultCodeEnum;
import com.cskaoyan.mall.common.result.Result;
import com.cskaoyan.mall.common.util.IpUtil;
import com.cskaoyan.mall.user.consts.UserConstants;
import com.cskaoyan.mall.user.dto.UserLoginInfoDTO;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * 在网关中拦截，验证用户登录身份
 * @author Macro_Ray
 * @since 2024/6/23 下午5:53
 */
@Component
public class AuthGlobalFilter implements GatewayFilter, Ordered {

    @Autowired
    RedissonClient redissonClient;
    @Value("${authUrls.url}")
    String authUrl;

    private final AntPathMatcher matcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1 获取当前拦截的请求
        ServerHttpRequest request = exchange.getRequest();

        // 获取当前请求的请求路径
        String path = request.getURI().getPath();
        if (matcher.match("/**/inner/**", path)) {
            // 内部的服务调用请求
            return out(exchange.getResponse(),ResultCodeEnum.PERMISSION);
        }

        // 2 从请求中获取 Cookie 中的token字符串，并根据 token 字符串获取redis中的登录信息
        String userId = getUserId(request);

        // 处理 userId 为 -1 的情况，非法登录，表示用户信息可能被盗用
        if (userId.equals("-1")){
            return out(exchange.getResponse(), ResultCodeEnum.PERMISSION);  // 返回209错误码，没有权限
        }

        // 3 判断请求是否需要登录
        // 匹配登录的请求路径模式
        if (matcher.match(authUrl, path)){
            // 需要登录
            if (userId.isEmpty()){
                // 需要登录但是未登录，重定向到登录页面
                return out(exchange.getResponse(), ResultCodeEnum.LOGIN_AUTH);
            }
        }
        // 要么不需要登陆，要么需要登陆且已经登陆
        // (1) 如果用户登录过了，将 userId 放入到请求头中
        ServerHttpRequest.Builder newRequestBuilder = request.mutate();
        if (!userId.isEmpty()) {
            // 如果登录了，将 userId 放入自定义请求头，转发给服务
            newRequestBuilder.header("userId", userId);
        }
        // (2) 如果前端传了userTempId, 获取该用户临时Id    临时Id
        String userTempId = getInfoByName(request, "userTempId");
        if (userTempId != null && !userTempId.isEmpty()) {
            newRequestBuilder.header("userTempId", userTempId);
        }
        ServerHttpRequest newRequest = newRequestBuilder.build();

        // 将新的Request对象，放入exchange
        ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();

        return chain.filter(newExchange);
    }

    /** 从Redis中获取用户id*/
    private String getUserId(ServerHttpRequest request) {

        String tokenName = UserConstants.USER_LOGIN_TOKEN_HEADER;
        String tokenStr = getInfoByName(request, tokenName);
        // 判断token字符串是否为空
        if (tokenStr!= null){
            // 没有获取到token字符串
            return "";
        }
        // 获取到token字符串，访问redis获取登录信息
        RBucket<UserLoginInfoDTO> bucket = redissonClient.getBucket(tokenName + tokenStr);
        if (bucket.get() == null){
            // redis中没有用户信息
            return "";
        }

        UserLoginInfoDTO userLoginInfoDTO = bucket.get();

        // 判断用户登录时的ip和当前请求的ip是否一致
        String gatewayIpAddress = IpUtil.getGatwayIpAddress(request);
        if (!gatewayIpAddress.equals(userLoginInfoDTO.getIp())){
            // ip不一致，用户的信息可能被盗用
            return "-1";
        }
        // ip一致，返回用户id
        return userLoginInfoDTO.getUserId();
    }

    /** 获取token字符串*/
    private static String getInfoByName(ServerHttpRequest request, String tokenName) {

        // 1 从请求头中获取
        HttpHeaders headers = request.getHeaders();
        List<String> headerValues = headers.get(tokenName);
        if (headerValues != null && !headerValues.isEmpty()) {
            // 登录信息在请求头中
            return headerValues.get(0);
        }

        // 2 请求头中没有登录信息，从Cookie中获取
        // 获取所有Cookie
        MultiValueMap<String, HttpCookie> cookies = request.getCookies();
        // 获取指定Cookie
        List<HttpCookie> httpCookies = cookies.get(tokenName);
        if (httpCookies == null || httpCookies.isEmpty()) {
            // Cookie中没有登录信息
            return "";
        }
        // Cookie中有登录信息，获取Cookie中的token字符串
        return httpCookies.get(0).getValue();
    }

    /**适配Mono*/
    private Mono<Void> out(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) {
        // 1. 构造Result对象
        Result<Object> build = Result.build(null, resultCodeEnum);

        // 2. 将Result对象转化为json字符串
        String jsonString = JSON.toJSONString(build);

        // 3. 获取json字符串对应的字节数据
        // byte[] bytes = jsonString.getBytes(Charset.forName("utf-8"));
        byte[] bytes = jsonString.getBytes(StandardCharsets.UTF_8);

        // 4. 封装字节数据
        DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);

        // 5. 设置一下响应所使用的字符集
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");

        return response.writeWith(Mono.just(dataBuffer));
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
